package away3d.bounds ;


import away3d.core.math.Matrix3DUtils ;
import away3d.primitives.WireframePrimitiveBase ;
import away3d.primitives.WireframeSphere ;
import flash.geom.Matrix3D ;
import flash.geom.Vector3D ;



/**
 * BoundingSphere represents a spherical bounding volume defined by a center point and a radius?
 * This bounding volume is useful for point lights.
 */
class BoundingSphere extends BoundingVolumeBase
{
    
    
    private var _radius     : Float ;
    private var _centerX    : Float ;
    private var _centerY    : Float ;
    private var _centerZ    : Float ;
    
    
    
    /**
     * Creates a new BoundingSphere object
     */
    public function new()
    {
        
        _radius     = 0 ;
        _centerX    = 0 ;
        _centerY    = 0 ;
        _centerZ    = 0 ;
        
    }
    
    
    override private function updateBoundingRenderable()
    {
        var sc : Float = _radius ;
        
        if( sc == 0 ) sc = 0.001 ;
        
        _boundingRenderable.scaleX  = sc ;
        _boundingRenderable.scaleY  = sc ;
        _boundingRenderable.scaleZ  = sc ;
        _boundingRenderable.x       = _centerX ;
        _boundingRenderable.y       = _centerY ;
        _boundingRenderable.z       = _centerZ ;
        
    }
    
    
    override private function createBoundingRenderable() : WireframePrimitiveBase
    {
        
        return new WireframeSphere( 1 ) ;
        
    }
    
    
    /**
     * @inheritDoc
     */
    override public function nullify()
    {
        
        super.nullify() ;
        _centerX = _centerY = _centerZ = 0 ;
        _radius = 0 ;
        
    }
    
    
    /**
     * todo: pass planes?
     * @inheritDoc
     */
    /*override public function isInFrustum( mvpMatrix : Matrix3D ) : Bool
     {
     var raw : Vector<Float> = Matrix3DUtils.RAW_DATA_CONTAINER ;
     mvpMatrix.copyRawDataTo( raw ) ;
     var c11 : Float = raw[uint( 0 )], c12 : Float = raw[uint( 4 )], c13 : Float = raw[uint( 8 )], c14 : Float = raw[uint( 12 )] ;
     var c21 : Float = raw[uint( 1 )], c22 : Float = raw[uint( 5 )], c23 : Float = raw[uint( 9 )], c24 : Float = raw[uint( 13 )] ;
     var c31 : Float = raw[uint( 2 )], c32 : Float = raw[uint( 6 )], c33 : Float = raw[uint( 10 )], c34 : Float = raw[uint( 14 )] ;
     var c41 : Float = raw[uint( 3 )], c42 : Float = raw[uint( 7 )], c43 : Float = raw[uint( 11 )], c44 : Float = raw[uint( 15 )] ;
     var a : Float, b : Float, c : Float, d : Float ;
     var dd : Float, rr : Float = _radius ;

     // todo: this can be ( much ) faster: http://www.racer.nl/reference/vfc_markmorley.htm

     // left plane
     a = c41 + c11 ;
     b = c42 + c12 ;
     c = c43 + c13 ;
     d = c44 + c14 ;
     dd = a * _centerX + b * _centerY + c * _centerZ ;
     if( a < 0 ) a = -a ;
     if( b < 0 ) b = -b ;
     if( c < 0 ) c = -c ;
     rr = ( a + b + c ) * _radius ;
     if( dd + rr < -d ) return false ;
     // right plane
     a = c41 - c11 ;
     b = c42 - c12 ;
     c = c43 - c13 ;
     d = c44 - c14 ;
     dd = a * _centerX + b * _centerY + c * _centerZ ;
     if( a < 0 ) a = -a ;
     if( b < 0 ) b = -b ;
     if( c < 0 ) c = -c ;
     rr = ( a + b + c ) * _radius ;
     if( dd + rr < -d ) return false ;
     // bottom plane
     a = c41 + c21 ;
     b = c42 + c22 ;
     c = c43 + c23 ;
     d = c44 + c24 ;
     dd = a * _centerX + b * _centerY + c * _centerZ ;
     if( a < 0 ) a = -a ;
     if( b < 0 ) b = -b ;
     if( c < 0 ) c = -c ;
     rr = ( a + b + c ) * _radius ;
     if( dd + rr < -d ) return false ;
     // top plane
     a = c41 - c21 ;
     b = c42 - c22 ;
     c = c43 - c23 ;
     d = c44 - c24 ;
     dd = a * _centerX + b * _centerY + c * _centerZ ;
     if( a < 0 ) a = -a ;
     if( b < 0 ) b = -b ;
     if( c < 0 ) c = -c ;
     rr = ( a + b + c ) * _radius ;
     if( dd + rr < -d ) return false ;
     // near plane
     a = c31 ;
     b = c32 ;
     c = c33 ;
     d = c34 ;
     dd = a * _centerX + b * _centerY + c * _centerZ ;
     if( a < 0 ) a = -a ;
     if( b < 0 ) b = -b ;
     if( c < 0 ) c = -c ;
     rr = ( a + b + c ) * _radius ;
     if( dd + rr < -d ) return false ;
     // far plane
     a = c41 - c31 ;
     b = c42 - c32 ;
     c = c43 - c33 ;
     d = c44 - c34 ;
     dd = a * _centerX + b * _centerY + c * _centerZ ;
     if( a < 0 ) a = -a ;
     if( b < 0 ) b = -b ;
     if( c < 0 ) c = -c ;
     rr = ( a + b + c ) * _radius ;
     if( dd + rr < -d ) return false ;

     return true ;
     }  */
    
    
    override public function isInFrustum( mvpMatrix : Matrix3D ) : Bool
    {
        var raw = Matrix3DUtils.RAW_DATA_CONTAINER ;
        mvpMatrix.copyRawDataTo( raw ) ;
        
        var c11     = raw[ uint( 0 ) ] ;
        var c12     = raw[ uint( 4 ) ] ;
        var c13     = raw[ uint( 8 ) ] ;
        var c14     = raw[ uint( 12 ) ] ;
        var c21     = raw[ uint( 1 ) ] ;
        var c22     = raw[ uint( 5 ) ] ;
        var c23     = raw[ uint( 9 ) ] ;
        var c24     = raw[ uint( 13 ) ] ;
        var c31     = raw[ uint( 2 ) ] ;
        var c32     = raw[ uint( 6 ) ] ;
        var c33     = raw[ uint( 10 ) ] ;
        var c34     = raw[ uint( 14 ) ] ;
        var c41     = raw[ uint( 3 ) ] ;
        var c42     = raw[ uint( 7 ) ] ;
        var c43     = raw[ uint( 11 ) ] ;
        var c44     = raw[ uint( 15 ) ] ;
        
        var a       : Float ;
        var b       : Float ;
        var c       : Float ;
        var d       : Float ;
        var negRad  : Float     = -_radius ;
        
        // left plane
        
        a = c41 + c11 ;
        b = c42 + c12 ;
        c = c43 + c13 ;
        d = c44 + c14 ;
        
        if( a * _centerX + b * _centerY + c * _centerZ + d <= negRad )
        {
            
            return false ;
            
        }
        
        // right plane
        
        a = c41 - c11 ;
        b = c42 - c12 ;
        c = c43 - c13 ;
        d = c44 - c14 ;
        
        if( a * _centerX + b * _centerY + c * _centerZ + d <= negRad )
        {
            
            return false ;
            
        }
        
        // bottom plane
        
        a = c41 + c21 ;
        b = c42 + c22 ;
        c = c43 + c23 ;
        d = c44 + c24 ;
        
        if( a * _centerX + b * _centerY + c * _centerZ + d <= negRad )
        {
            
            return false ;
            
        }
        
        // top plane
        
        a = c41 - c21 ;
        b = c42 - c22 ;
        c = c43 - c23 ;
        d = c44 - c24 ;
        
        if( a * _centerX + b * _centerY + c * _centerZ + d <= negRad )
        {
            
            return false ;
            
        }
        
        // near plane
        
        a = c31 ;
        b = c32 ;
        c = c33 ;
        d = c34 ;
        
        if( a * _centerX + b * _centerY + c * _centerZ + d <= negRad )
        {
            
            return false ;
            
        }
        
        // far plane
        
        a = c41 - c31 ;
        b = c42 - c32 ;
        c = c43 - c33 ;
        d = c44 - c34 ;
        
        if( a * _centerX + b * _centerY + c * _centerZ + d <= negRad )
        {
            
            return false ;
            
        }
        
        return true ;
        
    }
    
    
    /**
     * @inheritDoc
     */
    override public function fromSphere( center : Vector3D, radius : Float )
    {
        
        _centerX            = center.x ;
        _centerY            = center.y ;
        _centerZ            = center.z ;
        _radius             = radius ;
        _max.x              = _centerX + radius ;
        _max.y              = _centerY + radius ;
        _max.z              = _centerZ + radius ;
        _min.x              = _centerX - radius ;
        _min.y              = _centerY - radius ;
        _min.z              = _centerZ - radius ;
        _aabbPointsDirty    = true ;
        
        if( _boundingRenderable ) updateBoundingRenderable() ;
        
    }
    
    
    // todo: fromGeometry can probably be updated a lot
    // find center from extremes, but radius from actual furthest distance from center
    
    
    /**
     * @inheritDoc
     */
    override public function fromExtremes( minX : Float, minY : Float, minZ : Float, maxX : Float, maxY : Float, maxZ : Float )
    {
        
        _centerX        = ( maxX + minX ) * .5 ;
        _centerY        = ( maxY + minY ) * .5 ;
        _centerZ        = ( maxZ + minZ ) * .5 ;
        
        var d           = maxX - minX ;
        var y           = maxY - minY ;
        var z           = maxZ - minZ ;
        if( y > d ) d   = y ;
        if( z > d ) d   = z ;
        
        _radius         = d * Math.sqrt( .5 ) ;
        
        super.fromExtremes( minX, minY, minZ, maxX, maxY, maxZ ) ;
        
    }
    
    
    /**
     * @inheritDoc
     */
    override public function clone() : BoundingVolumeBase
    {
        
        var clone = new BoundingSphere() ;
        clone.fromSphere( new Vector3D( _centerX, _centerY, _centerZ ), _radius ) ;
        return clone ;
        
    }
    
    
    override public function intersectsLine( p : Vector3D, dir : Vector3D ) : Bool
    {
        
        var cx  = p.x - _centerX ; 
        var cy  = p.y - _centerY ;
        var cz  = p.z - _centerZ ;
        var det = cx * cx + cy * cy + cz * cz - _radius * _radius ;
        
        if( det <= 0 ) return true ;
        
        var dot = dir.x * cx + dir.y * cy + dir.z * cz ;
        
        return dot * dot - det >= 0 ;
        
    }
    
    
    override public function intersectsRay( p : Vector3D, dir : Vector3D ) : Bool
    {
        
        var cx  = p.x - _centerX ;
        var cy  = p.y - _centerY ; 
        var cz  = p.z - _centerZ ;
        var det = cx * cx + cy * cy + cz * cz - _radius * _radius ;
        
        if( det <= 0 ) return true ;
        
        var dot = dir.x * cx + dir.y * cy + dir.z * cz ;
        
        // ray going away from sphere
        if( dot >= 0 ) return false ;
        
        return dot * dot - det >= 0 ;
    
    }
    
    
}